package com.kasax.plugin.app.transform

import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.utils.FileUtils
import com.kasax.plugin.app.LogUtils
import com.kasax.plugin.app.visitor.LifecycleClassVisitor
import org.apache.commons.codec.digest.DigestUtils
import org.apache.commons.io.IOUtils
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes

import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry

/**
 * https://www.jianshu.com/p/16ed4d233fd1*/
class MethodLifecycleTransform extends Transform {

    @Override
    String getName() {
        return "LifeCountTransform"
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        LogUtils.d(getName(), "------------ ------------  transform 开始  ------------ ------------  ")
        def startTime = System.currentTimeMillis()
        def inputs = transformInvocation.inputs
        def outputs = transformInvocation.outputProvider
        if (outputs != null) {
            // 删除之前的输出
            outputs.deleteAll()
        }
        // 遍历inputs
        inputs.each { TransformInput input ->
            // 依赖的jar包内容保持不变
            LogUtils.d(getName(), "handle jarInputs")
            // 遍历jarInputs
            input.jarInputs.each { JarInput jarInput -> handleJarInput(jarInput, outputs)
            }
            LogUtils.d(getName(), "handle directoryInputs")

            // 遍历directoryInputs
            // 当前项目源码
            input.directoryInputs.each { DirectoryInput directoryInput ->
                // 处理字节码
                handleDirInput(directoryInput, outputs)

            }
        }
        def cost = System.currentTimeMillis() - startTime
        LogUtils.d(getName(), "------------ ------------  transform 结束  ------------ ------------  ")
        LogUtils.d(getName(), "transform cost-time: $cost")
    }

    /**
     * 处理jar中的class文件
     * @param jarInput
     */
    void handleJarInput(JarInput jarInput, TransformOutputProvider outputProvider) {
        // jarInput 为jar包中的class
        if (jarInput.file.absolutePath.endsWith(".jar")) {
            // 重命名输出文件，因为可能会同名，会覆盖
            def jarName = jarInput.name
            def md5 = DigestUtils.md5Hex(jarInput.file.absolutePath)
            if (jarName.endsWith(".jar")) {
                jarName = jarName.substring(0, jarName.length() - 4)
            }
            JarFile jarFile = new JarFile(jarInput.file)
            def enumeration = jarFile.entries()
            def tempFile = new File(jarInput.file.getParent() + File.separator + "classes_temp.jar")
            // 避免上次的缓存被重复插入
            if (tempFile.exists()) {
                tempFile.delete()
            }
            def jarOs = new JarOutputStream(new FileOutputStream(tempFile))
            // 用于保存
            while (enumeration.hasMoreElements()) {
                def jarEntry = enumeration.nextElement()
                String entryName = jarEntry.name
                def zipEntry = new ZipEntry(entryName)
                InputStream ins = jarFile.getInputStream(zipEntry)
                // 插桩class
                if (isSupportCls(entryName)) {
                    // class文件处理
                    LogUtils.d(getName(), "handleJarInput deal with class file < $name > ------")

                    jarOs.putNextEntry(zipEntry)
                    def classReader = new ClassReader(IOUtils.toByteArray(ins))
                    def classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
                    def cv = new LifecycleClassVisitor(classWriter)
                    classReader.accept(cv, ClassReader.EXPAND_FRAMES)
                    def code = classWriter.toByteArray()
                    jarOs.write(code)
                } else {
                    jarOs.putNextEntry(zipEntry)
                    jarOs.write(IOUtils.toByteArray(ins))
                }
                jarOs.closeEntry()
            }
            // 结束
            jarOs.closeEntry()
            jarFile.close()
            // 传递给下一个任务
            def dest = outputProvider.getContentLocation(jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
            FileUtils.copyFile(jarInput.file, dest)
        }
    }

    /**
     * 处理目录中的class文件
     * @param directoryInput
     */
    void handleDirInput(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {
        // directoryInput 为文件夹中的class
        if (directoryInput.file.isDirectory()) {
            directoryInput.file.eachFileRecurse { File file ->
                def dirName = file.name
                def fullPath = file.absolutePath
                if (isSupportCls(fullPath)) {
                    LogUtils.d(getName(), "handleDirInput deal with class file < $dirName > ------")
                    try {
                        def classReader = new ClassReader(file.bytes)
                        def classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
                        def cv = new LifecycleClassVisitor(classWriter)
                        classReader.accept(cv, ClassReader.EXPAND_FRAMES)

                        def code = classWriter.toByteArray()
                        // 验证生成的字节码是否完整
                        if (code != null && code.length > 0) {
                            def fos = new FileOutputStream(file.parentFile.absolutePath + File.separator + dirName)
                            fos.write(code)
                            fos.close()
                        } else {
                            LogUtils.e(getName(), "Generated bytecode is empty for file: $dirName")
                        }
                    } catch (Exception e) {
                        LogUtils.e(getName(), "Error processing class file: $dirName, error: ${e.message}")
                    }
                }
            }
        }
        //处理完输入文件之后，要把输出给下一个任务
        def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes,
                directoryInput.scopes, Format.DIRECTORY)
        FileUtils.copyDirectory(directoryInput.file, dest)
    }

    private boolean isSupportCls(String clsName) {
        LogUtils.d(getName(), "isSupportCls-name: $clsName")
        if (!clsName.endsWith(".class") || clsName.startsWith("R\$") || "R.class".equals(clsName) || "BuildConfig.class".equals(clsName)) {
            LogUtils.d(getName(), "Filtered out: $clsName")
            return false
        }

        // 动态检查类是否为 AppCompatActivity 或其子类
        try {
            def filePath = clsName.replace("/", File.separator)
            LogUtils.d(getName(), "Checking class file: $filePath")
            def classReader = new ClassReader(new FileInputStream(filePath))
            def classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS)
            def visitor = new ClassVisitor(Opcodes.ASM7, classWriter) {
                @Override
                void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
                    LogUtils.d(getName(), "Visiting class: $name, superName: $superName")
                    if (superName != null && (superName.equals("androidx/appcompat/app/AppCompatActivity") || superName.equals("android/support/v4/app/FragmentActivity"))) {
                        LogUtils.d(getName(), "Found supported class: $name")
                        throw new RuntimeException("Found supported class")
                    }
                }
            }
            classReader.accept(visitor, ClassReader.EXPAND_FRAMES)
            LogUtils.d(getName(), "Class not supported: $clsName")
            return false
        } catch (Exception e) {
            if (e.message?.contains("Found supported class")) {
                LogUtils.d(getName(), "Class supported: $clsName")
                return true
            }
            LogUtils.d(getName(), "Error checking class: $clsName, error: ${e.message}")
            return false
        }
    }
}